import os

import bpy
from bpy.props import BoolProperty
from bpy.types import Operator

from ..utils.log import log
from .asset_utils import AssetUtils as ImportAsset
from ..utils.preferences import Preferences
from ..utils.preferences.asset_items import AssetItem


class SwitchRenderUi(Operator):
    bl_label = '弹出窗口'
    bl_idname = 'wm.popup_render_window'
    bl_description = '弹出渲染设置窗口'

    popup_window: BoolProperty(default=False, name='弹出窗口',
                               options={'SKIP_SAVE', })

    def execute(self, context):
        if self.popup_window:
            bpy.ops.screen.userpref_show()

        from ..ui import PreferencesUI
        PreferencesUI.switch()
        self.show_header(context)
        context.preferences.active_section = 'ADDONS'
        bpy.ops.wm.redraw_timer(type='DRAW_WIN')
        return {'FINISHED'}

    @staticmethod
    def show_header(context: "bpy.types.Context"):
        def set_header(areas: list[bpy.types.Area]):
            for area in areas:
                if area.type == 'PREFERENCES':
                    area.spaces[0].show_region_header = True

        for win in context.window_manager.windows:
            set_header(win.screen.areas)


class MultipleLocationBox(Preferences):
    """多定位框处理类"""
    assign_multiple_data: dict

    def multiple_assign_init(self) -> None:
        """初始化多个匹配的数据
        清理预览集合
        """
        ImportAsset.clear_preview_collection()
        if not self.assign_multiple_data:
            self.assign_multiple_data = {obj: {
                # 抽奖池,将所有的比例放进去,随机抽一个出来
                'pool': [index for index, matching in enumerate(obj.render_master.render_matching)
                         for _ in range(matching.render_matching_probability)],
                # 'flag': {},  # TODO 导入一次+1,后续可用来做避免重复出现判断
                'collection': {},  # 将已经导入了的资产存下来
            }
                for obj in self.location_box_list
            }

    def import_object_tag_assign_multiple(self, scene: 'AssetItem', out_folder: str) -> str:
        """导入物体tag_多个分配
        导入资产到输入定位框
        将导入资产放到预览集合下

        清理集合内容(将预览集合清空,而不是删除,免得资产多次出现时会出现重复导入的性能消耗)
        获取定位物体列表
        在定位物体里面抽一个项
        在项里面抽一个资产路径导入
        导入到活动场景并存下来
        TODO 导入到预览集合
        TODO 变换到定位框
        TODO 反回导入的名称
        TODO 反回输出路径

        if len(self.location_box_list) > 1:
            # 有一个以上的定位框
            for location in self.location_box_list:
                self.import_object(
                    scene, object, out_folder, location)
            return os.path.join(out_folder, file_name.replace('-', '_'))
        else:
            return self.import_object(scene, object, out_folder)
        """
        self.multiple_assign_init()
        box_list = self.location_box_list
        file_name = f"{scene.asset_name}___"

        for obj in box_list:
            if obj.render_master.assets_items_path:  # 定位box至少需要有一个资产可用

                file_name += (obj.render_master.import_asset(
                    obj, self.assign_multiple_data) + '_')

        return os.path.join(out_folder, file_name + '.blend')


class ExportRenderFile(Operator,
                       MultipleLocationBox,
                       ):
    bl_idname = 'asset.render_file_export'
    bl_label = '输出渲染文件'
    bl_description = '''
    仅输出blend文件,不对渲染进行设置
    TODO 添加输出文件到渲染队列,渲染队列单独做一个插件
    导入做为一个单独的操作符
    使用重复导入来设置
    TODO 多TAG-时导入
    '''
    bl_options = {'INTERNAL'}

    is_popup_menu: BoolProperty(default=True)

    fixed_object_collection = None  # TODO 固定物体,用于固定物体导入模式时使用 还原矩阵
    assign_multiple_data = {}  # 存储多定位框分配的问题{obj:{pool:[],asset}}

    @property
    def file_out_folder(self) -> str:
        """渲染文件输出路径"""
        return bpy.context.scene.render.filepath

    @property
    def render_camera(self) -> bool:
        """反回场景内是否有相机的布尔值"""
        if bpy.context.scene.camera:
            return True
        for obj in bpy.context.scene.objects:
            if obj.type == 'CAMERA':
                bpy.context.scene.camera = obj
                return True
            
        return False

    @classmethod
    def poll(cls, context) -> bool:
        """TODO 只有文件被保存了
        并且渲染
        return bpy.data.is_saved
        """
        return True

    def draw(self, context):
        """绘制添加提示"""
        from ..ui.asset_layout import AssetLayout
        layout = self.layout
        layout.alert = True
        layout.label(text='添加到渲染队列将会在添加完成后新建初始文件')
        layout.label(text='并且blender主进程会无响应')
        layout.label(text='建议先保存文件')
        layout.separator()
        layout.label(text='请选择一个绝对路径文件夹')
        layout.label(text='相对路径无法自动将文件加载到渲染队列')
        AssetLayout.draw_queue_left_export_info(layout)

    def invoke(self, context: 'bpy.context', event):
        if self.is_popup_menu:
            return context.window_manager.invoke_props_dialog(self)
        return {'FINISHED'}

    def execute(self, context):
        self.set_import_asset_property()
        self.generate_render_file()
        log.info('generate_render_file')
        return {'FINISHED'}

    def set_import_asset_property(self):
        """设置导入资产的属性
        替换功能是用的这个
        TODO 设置为父级
        """

        pref = self.pref
        pref.import_link = False
        pref.import_mode = 'PREVIEW'
        pref.import_set_parent_form_tag = False
        pref.import_to_active_collect = False
        pref.import_use_fake_user = False
        pref.import_to_cursor = False

        pref.import_whole_scene = True
        pref.import_to_location_box = True

    @staticmethod
    def all_operator_listen() -> list[str]:
        """反回所有操作符列表
        """
        from _bpy import ops as _ops_module
        _op_dir = _ops_module.dir
        submodules = set()
        for id_name in _op_dir():
            id_split = id_name.split("_OT_", 1)
            if len(id_split) == 2:
                submodules.add(id_split[0].lower() + '.' + id_split[1])
        return list(submodules)

    @staticmethod
    def add_render_queue(file_path: str):
        """新建渲染队列，分离为单个插件
        obj, scene, asset_path, asset_name
        """

        if 'render.render_queue_add_element' in ExportRenderFile.all_operator_listen():
            path = os.path.abspath(file_path)
            log.debug(f"add render_queue {path}")
            bpy.ops.render.render_queue_add_element(filepath=path)

    @property
    def scene_items(self):
        return self.get_selected_assets('scene')

    @property
    def object_items(self) -> 'list[preferences.asset_items.AssetItem]':
        if self.is_scene_mode:
            return [None for _ in range(self.pref.export_assign_file_count)]

        return self.get_selected_assets('object')

    def generate_render_file(self):
        """生成渲染文件
        POOL 正常生成 循环所选资产
        SCENE 导入所选场景,只循环所选的物体 或者是自定义定位框多个导入
        OBJECT 导入所选物体,只循环所选场景
        # bpy.ops.scene.new(type='EMPTY')
        # 复制一个场景出来,避免物体出错,同时把渲染设置复制了出来
        """

        out_folder = self.file_out_folder  # 输入渲染blender文件的路径
        self.mark_dir(out_folder)

        for scene in self.scene_items:  # 循环每个已选择的场景
            self.import_scene(scene)
            for obj in self.object_items:  # 产品
                out_path = self.import_object(scene, obj, out_folder)

                if not self.render_camera:
                    # 场景内没有相机,取消
                    self.report(
                        {'INFO'}, f'场景内无相机,跳过{scene.asset_name, obj.asset_name}')

                if out_path:
                    self.rewrite_file(out_path)  # 删除已存在文件
                    self.write_blend(out_path)  # 写入文件

    @staticmethod
    def mark_dir(out_path: str) -> None:
        """生成输出文件夹路径,用作导出时使用的文件夹目录
        如果没有输出文件"""
        if not os.path.isdir(out_path):
            os.makedirs(out_path)  # 如果没有这个文件夹则当场新建一个

    def import_scene(self, scene: 'AssetItem') -> None:
        """导入场景"""
        if self.is_scene_mode:
            # 如果是固定-场景模式就不执行导入，已经导入过了
            return
        self.import_asset(scene)

    def import_object(self, scene: 'AssetItem', obj: 'AssetItem', out_folder: str) -> str:
        """导入物体
        反回输出的文件路径
        """
        if self.is_scene_mode:
            #  场景模式单独处理
            return self.import_object_tag_assign_multiple(scene, out_folder)
        elif self.is_object_mode:
            self.fixed_object_import(scene, obj, out_folder)
        elif self.is_pool_mode:
            self.import_asset(obj)
        else:
            return "None" + self

        file_name = f'{scene.asset_name}_{obj.asset_name}.blend'
        out_file_path = os.path.join(out_folder, file_name)

        return out_file_path

    @classmethod
    def rewrite_file(cls, blend_file_path) -> None:
        """重写输出文件,如果文件存在则删除"""
        if os.path.isfile(blend_file_path):
            os.remove(blend_file_path)  # 如果已经有同名的文件则删掉再写一次

    def write_blend(self, file_path: str) -> None:
        """写入文件活动场景到文件
        在写入后添加到队列"""

        bpy.context.scene.update_tag()

        bpy.data.libraries.write(
            file_path, {bpy.context.scene, })  # 写入数据
        self.add_render_queue(file_path)

    @staticmethod
    def _import_asset_get(asset_item: 'AssetItem') -> 'ImportAsset':
        asset = ImportAsset()  # 将操作符的操作分离出来,单独使用
        asset.asset_path = asset_item.asset_path
        asset.asset_type = asset_item.asset_type
        return asset

    @staticmethod
    def import_asset(asset_item: 'AssetItem') -> 'ImportAsset':
        """输入一个资产用于导入"""
        asset = ExportRenderFile._import_asset_get(asset_item)
        asset.import_asset(bpy.context)  # 直接使用默认的导入
        return asset

    def fixed_object_import(self, scene: 'AssetItem', obj: 'AssetItem', out_folder: str):
        """固定物体导入
        只导入物体一次
        第一次导入后存下来
        其它的场景只关联使用
        将导入方法提取出来
        还有关联方法
        导入矩阵设置
        """
        asset = ExportRenderFile._import_asset_get(obj)
        box = self.location_box
        col = asset.from_file_load_collection()
        bpy.context.scene.collection.children.link(col)
        asset.collection_translation_to_object(col, box)


class_tuple = (
    SwitchRenderUi,
    ExportRenderFile,
)

reg_class, unreg_class = bpy.utils.register_classes_factory(class_tuple)


def register():
    reg_class()


def unregister():
    unreg_class()
